package edu.mse.camel.client.whiteboard.gef.ui.editor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.EventObject;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.ShortestPathConnectionRouter;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
import org.eclipse.gef.ContextMenuProvider;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.editparts.ScalableRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.dnd.TemplateTransferDragSourceListener;
import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.requests.CreationFactory;
import org.eclipse.gef.requests.SimpleFactory;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.gef.ui.palette.PaletteViewerProvider;
import org.eclipse.gef.ui.palette.FlyoutPaletteComposite.FlyoutPreferences;
import org.eclipse.gef.ui.parts.ContentOutlinePage;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.gef.ui.parts.TreeViewer;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.util.TransferDropTargetListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.gef.ui.actions.ZoomInAction;
import org.eclipse.gef.ui.actions.ZoomOutAction;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.KeyStroke;
import org.eclipse.swt.SWT;


import edu.mse.camel.client.whiteboard.gef.ui.editpart.WhiteboardEditPartFactory;
import edu.mse.camel.client.whiteboard.gef.ui.editpart.WhiteboardTreeEditpartfactory;
import edu.mse.camel.client.whiteboard.gef.whiteboard.WhiteboardDiagram;

/**
 * A graphical editor with flyout palette that can edit .whiteboard files.
 * The binding between the .whiteboard file extension and this editor is done in plugin.xml
 * @author darpan
 */
public class WhiteboardEditor 
extends GraphicalEditorWithFlyoutPalette 
{
	
	/** This is the root of the editor's model. */
	private WhiteboardDiagram diagram;
	/** Palette component, holding the tools and shapes. */
	private static PaletteRoot PALETTE_MODEL;
	
	private final Resource resource = new XMIResourceImpl();
	private KeyHandler keyHandler;
	/** Create a new ShapesEditor instance. This is called by the Workspace. */
	public WhiteboardEditor() {
		setEditDomain(new DefaultEditDomain(this));
	}
	
	/**
	 * Configure the graphical viewer before it receives contents.
	 * <p>This is the place to choose an appropriate RootEditPart and EditPartFactory
	 * for your editor. The RootEditPart determines the behavior of the editor's "work-area".
	 * For example, GEF includes zoomable and scrollable root edit parts. The EditPartFactory
	 * maps model elements to edit parts (controllers).</p>
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#configureGraphicalViewer()
	 */
	protected void configureGraphicalViewer() {
		super.configureGraphicalViewer();
		double[] zoomLevels;
		GraphicalViewer viewer = getGraphicalViewer();
		ScalableRootEditPart rootEditPart = new ScalableRootEditPart();
		viewer.setEditPartFactory(new WhiteboardEditPartFactory());
		viewer.setRootEditPart(rootEditPart);
		viewer.setKeyHandler(new GraphicalViewerKeyHandler(viewer));

		 ZoomManager manager = rootEditPart.getZoomManager();
		 getActionRegistry().registerAction(new ZoomInAction(manager));
		 getActionRegistry().registerAction(new ZoomOutAction(manager));
		 
		 zoomLevels = new double[] {0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 10.0, 20.0};
		 manager.setZoomLevels(zoomLevels);
		 ArrayList<String> zoomContributions = new ArrayList<String>();
		 zoomContributions.add(ZoomManager.FIT_ALL);
		 zoomContributions.add(ZoomManager.FIT_HEIGHT);
		 zoomContributions.add(ZoomManager.FIT_WIDTH);
		 manager.setZoomLevelContributions(zoomContributions);
		 
		 
		// configure the context menu provider
		ContextMenuProvider cmProvider =
				new WhiteboardEditorContextMenuProvider(viewer, getActionRegistry());
		viewer.setContextMenu(cmProvider);
		getSite().registerContextMenu(cmProvider, viewer);
		//adding choi
		getActionRegistry().registerAction(new SaveImageAction(viewer));
		
		 keyHandler = new KeyHandler();

		 keyHandler.put(
				 KeyStroke.getPressed(SWT.DEL, 127, 0),
				 getActionRegistry().getAction(ActionFactory.DELETE.getId()));
		 viewer.setKeyHandler(keyHandler);	 
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#commandStackChanged(java.util.EventObject)
	 */
	public void commandStackChanged(EventObject event) {
		firePropertyChange(IEditorPart.PROP_DIRTY);
		super.commandStackChanged(event);
	}
	
	/**
	 * Strange... I really should just modify doSave/As but I'm using the least
	 * path of resistance :)
	 * 
	 * @param os
	 * @throws IOException
	 */
	private void createOutputStream(OutputStream os) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			resource.save(out,Collections.EMPTY_MAP);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#createPaletteViewerProvider()
	 */
	protected PaletteViewerProvider createPaletteViewerProvider() {
		return new PaletteViewerProvider(getEditDomain()) {
			protected void configurePaletteViewer(PaletteViewer viewer) {
				super.configurePaletteViewer(viewer);
				// create a drag source listener for this palette viewer
				// together with an appropriate transfer drop target listener, this will enable
				// model element creation by dragging a CombinatedTemplateCreationEntries 
				// from the palette into the editor
				// @see ShapesEditor#createTransferDropTargetListener()
				viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer));
			}
		};
	}
	
	/**
	 * Create a transfer drop target listener. When using a CombinedTemplateCreationEntry
	 * tool in the palette, this will enable model element creation by dragging from the palette.
	 * @see #createPaletteViewerProvider()
	 */
	private TransferDropTargetListener createTransferDropTargetListener() {
		return new TemplateTransferDropTargetListener(getGraphicalViewer()) {
			protected CreationFactory getFactory(Object template) {
				return new SimpleFactory((Class) template);
			}
		};
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void doSave(IProgressMonitor monitor) {
		try {
			resource.save(Collections.EMPTY_MAP);
			getCommandStack().markSaveLocation();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.ISaveablePart#doSaveAs()
	 */
	public void doSaveAs() {
		// Show a SaveAs dialog
		Shell shell = getSite().getWorkbenchWindow().getShell();
		SaveAsDialog dialog = new SaveAsDialog(shell);
		dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
		dialog.open();
		
		IPath path = dialog.getResult();	
		if (path != null) {
			// try to save the editor's contents under a different file name
			final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
			try {
				new ProgressMonitorDialog(shell).run(
						false, // don't fork
						false, // not cancelable
						new WorkspaceModifyOperation() { // run this operation
							public void execute(final IProgressMonitor monitor) {
								try {
									ByteArrayOutputStream out = new ByteArrayOutputStream();
									createOutputStream(out);
									file.create(
											new ByteArrayInputStream(out.toByteArray()), // contents
											true, // keep saving, even if IFile is out of sync with the Workspace
											monitor); // progress monitor
								}
								catch (CoreException ce) { ce.printStackTrace(); }
								catch (IOException ioe) { ioe.printStackTrace(); } 
							}
						});
				// set input to the new file
				setInput(new FileEditorInput(file));
				getCommandStack().markSaveLocation();
			}
			catch (InterruptedException ie) {
				// should not happen, since the monitor dialog is not cancelable
				ie.printStackTrace(); 
			}
			catch (InvocationTargetException ite) { 
				ite.printStackTrace(); 
			}
		} // if
	}
	
	public Object getAdapter(Class type) {
		if (type == IContentOutlinePage.class)
			return new ShapesOutlinePage(new TreeViewer());
		return super.getAdapter(type);
	}
	
	WhiteboardDiagram getModel() {
		return diagram;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#getPalettePreferences()
	 */
	protected FlyoutPreferences getPalettePreferences() {
		return WhiteboardEditorPaletteFactory.createPalettePreferences();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#getPaletteRoot()
	 */
	protected PaletteRoot getPaletteRoot() {
		if (PALETTE_MODEL == null)
			PALETTE_MODEL = WhiteboardEditorPaletteFactory.createPalette();
		return PALETTE_MODEL;
	}
	
	private void handleLoadException(Exception e) {
		System.err.println("** Load failed. Using default model. **");
		e.printStackTrace();
		diagram = WhiteboardModelManager.getFactory().createWhiteboardDiagram();
	}
	
	/**
	 * Set up the editor's inital content (after creation).
	 * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#initializeGraphicalViewer()
	 */
	protected void initializeGraphicalViewer() {
		super.initializeGraphicalViewer();
		super.initializeGraphicalViewer();
		GraphicalViewer viewer = getGraphicalViewer();
		viewer.setContents(getModel()); // set the contents of this editor
		
		// listen for dropped parts
		viewer.addDropTargetListener(createTransferDropTargetListener());
//		GraphicalViewer viewer = getGraphicalViewer();
//		viewer.setContents(getModel()); // set the contents of this editor
//		
//		// add the ShortestPathConnectionRouter
////		ScalableFreeformRootEditPart root = 
////			(ScalableFreeformRootEditPart)viewer.getRootEditPart();
//		ScalableRootEditPart root = (ScalableRootEditPart)viewer.getRootEditPart();
//		ConnectionLayer connLayer =
//			(ConnectionLayer)root.getLayer(LayerConstants.CONNECTION_LAYER);
//		GraphicalEditPart contentEditPart = (GraphicalEditPart)root.getContents();
//		ShortestPathConnectionRouter router = 
//			new ShortestPathConnectionRouter(contentEditPart.getFigure());
//		connLayer.setConnectionRouter(router);
//		//contentEditPart.getContentPane().addLayoutListener(router.getLayoutListener());
//		
//		// listen for dropped parts
//		viewer.addDropTargetListener(createTransferDropTargetListener());
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
	 */
	public boolean isSaveAsAllowed() {
		return true;
	}
	
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
	 */
	protected void setInput(IEditorInput input) {
		super.setInput(input);
		IFile file = ((IFileEditorInput) input).getFile();
		URI uri = URI.createPlatformResourceURI(file.getFullPath().toString());
		resource.setURI(uri);
		try {
			resource.load(Collections.EMPTY_MAP);
		} catch (IOException e) {
			e.printStackTrace();
		}
		diagram = (WhiteboardDiagram) resource.getContents().get(0);
		setPartName(file.getName());
		
//		try {
//			IFile file = ((IFileEditorInput) input).getFile();
//			ObjectInputStream in = new ObjectInputStream(file.getContents());
//			diagram = (WhiteboardDiagram) in.readObject();
//			in.close();
//			setPartName(file.getName());
//		} catch (IOException e) { 
//			handleLoadException(e); 
//		} catch (CoreException e) { 
//			handleLoadException(e); 
//		} catch (ClassNotFoundException e) { 
//			handleLoadException(e); 
//		}
	}
	
	/**
	 * Creates an outline pagebook for this editor.
	 */
	public class ShapesOutlinePage extends ContentOutlinePage {	
		/**
		 * Create a new outline page for the shapes editor.
		 * @param viewer a viewer (TreeViewer instance) used for this outline page
		 * @throws IllegalArgumentException if editor is null
		 */
		public ShapesOutlinePage(EditPartViewer viewer) {
			super(viewer);
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite)
		 */
		public void createControl(Composite parent) {
			// create outline viewer page
			getViewer().createControl(parent);
			// configure outline viewer
			getViewer().setEditDomain(getEditDomain());
			getViewer().setEditPartFactory(new WhiteboardTreeEditpartfactory());
			// configure & add context menu to viewer
			ContextMenuProvider cmProvider = new WhiteboardEditorContextMenuProvider(
					getViewer(), getActionRegistry()); 
			getViewer().setContextMenu(cmProvider);
			getSite().registerContextMenu(
					"org.eclipse.gef.examples.shapes.outline.contextmenu",
					cmProvider, getSite().getSelectionProvider());		
			// hook outline viewer
			getSelectionSynchronizer().addViewer(getViewer());
			// initialize outline viewer with model
			getViewer().setContents(getModel());
			// show outline viewer
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.ui.part.IPage#dispose()
		 */
		public void dispose() {
			// unhook outline viewer
			getSelectionSynchronizer().removeViewer(getViewer());
			PALETTE_MODEL = null;
			// dispose
			super.dispose();
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.ui.part.IPage#getControl()
		 */
		public Control getControl() {
			return getViewer().getControl();
		}
		
		/**
		 * @see org.eclipse.ui.part.IPageBookViewPage#init(org.eclipse.ui.part.IPageSite)
		 */
		public void init(IPageSite pageSite) {
			super.init(pageSite);
			ActionRegistry registry = getActionRegistry();
			IActionBars bars = pageSite.getActionBars();
			String id = ActionFactory.UNDO.getId();
			bars.setGlobalActionHandler(id, registry.getAction(id));
			id = ActionFactory.REDO.getId();
			bars.setGlobalActionHandler(id, registry.getAction(id));
			id = ActionFactory.DELETE.getId();
			bars.setGlobalActionHandler(id, registry.getAction(id));
		}
	}
	
}